Исследование объявлений о продаже квартир¶

В вашем распоряжении данные сервиса Яндекс.Недвижимость — архив объявлений о продаже квартир в Санкт-Петербурге и соседних населённых пунктов за несколько лет. Задача — установить параметры, влияющие на определение рыночной стоимости. Это позволит построить автоматизированную систему: она отследит аномалии и мошенническую деятельность.

По каждой квартире на продажу доступны два вида данных. Первые вписаны пользователем, вторые — получены автоматически на основе картографических данных. Например, расстояние до центра, аэропорта, ближайшего парка и водоёма.

Введение¶

В настоящем исследовании предстоит изучить, обработать представленные данные. Провести исследование и сделать выводы по поставленным вопросам.

В датасете в табличном формате CSV присутствуют следующие данные по квартирам, которые продавались в Санкт-Петербурге и Лениниградской области в 2014 - 2019 годах:

Описание данных

  • airports_nearest — расстояние до ближайшего аэропорта в метрах (м)
  • balcony — число балконов (шт)
  • ceiling_height — высота потолков (м)
  • cityCenters_nearest — расстояние до центра города (м)
  • days_exposition — сколько дней было размещено объявление (от публикации до снятия)
  • first_day_exposition — дата публикации
  • floor — этаж (номер)
  • floors_total — всего этажей в доме (шт)
  • is_apartment — апартаменты (булев тип)
  • kitchen_area — площадь кухни в квадратных метрах (м²)
  • last_price — цена на момент снятия с публикации (руб)
  • living_area — жилая площадь в квадратных метрах (м²)
  • locality_name — название населённого пункта
  • open_plan — свободная планировка (булев тип)
  • parks_around3000 — число парков в радиусе 3 км (шт)
  • parks_nearest — расстояние до ближайшего парка (м)
  • ponds_around3000 — число водоёмов в радиусе 3 км (шт)
  • ponds_nearest — расстояние до ближайшего водоёма (м)
  • rooms — число комнат (шт)
  • studio — квартира-студия (булев тип)
  • total_area — общая площадь квартиры в квадратных метрах (м²)
  • total_images — число фотографий квартиры в объявлении (шт)

План исследования¶

Шаг 1. Открыть файл с данными и изучить общую информацию¶

  • вывести общую информацию
  • построить гистограммы по измеряемым данным

Шаг 2 Предобработка данных¶

  • определить, где есть пропуски данных, устранить
  • определить, где есть дубликаты, принять меры по их удалению или исправлению
  • проверить аномальные значения, выполнить действия по исправлению
  • изучить типы данных в столбцах и изменить при необходимости для целей исследования

Шаг 3 Рассчитать значения по заданию проекта и добавить в таблицу дополнительные столбцы¶

  • цена одного квадратного метра;
  • день недели публикации объявления (0 — понедельник, 1 — вторник и так далее);
  • месяц публикации объявления;
  • год публикации объявления;
  • тип этажа квартиры (значения — «первый», «последний», «другой»);
  • расстояние до центра города в километрах (перевести из м в км и округлить до целых значений).

Шаг 4 Провеcти исследовательский анализ данных¶

Часть 1 Исследование столбцов с данными. Предварительные выводы¶

Изучить следующие параметры объектов:

  • общая площадь;
  • жилая площадь;
  • площадь кухни;
  • цена объекта;
  • количество комнат;
  • высота потолков;
  • этаж квартиры;
  • тип этажа квартиры («первый», «последний», «другой»);
  • общее количество этажей в доме;
  • расстояние до центра города в метрах;
  • расстояние до ближайшего аэропорта;
  • расстояние до ближайшего парка;
  • день и месяц публикации объявления.

Построить отдельные гистограммы для каждого из этих параметров. Описать наблюдения по параметрам в ячейке с типом markdown.

Часть 2 Изучить, как быстро продавались квартиры (days_exposition).¶
  • построить гистограмму;
  • посчитать среднее и медиану;
  • в ячейке типа markdown описать, сколько времени обычно занимает продажа. Какие продажи можно считать быстрыми, а какие — необычно долгими?
Часть 3 Какие факторы больше всего влияют на общую (полную) стоимость объекта?¶

Изучить, зависит ли цена от:

  • общей площади;
  • жилой площади;
  • площади кухни;
  • количества комнат;
  • этажа, на котором расположена квартира (первый, последний, другой);
  • даты размещения (день недели, месяц, год).

Построить графики, которые покажут зависимость цены от указанных выше параметров.

Часть 4 Посчитать среднюю цену одного квадратного метра в 10 населённых пунктах с наибольшим числом объявлений. Выделить населённые пункты с самой высокой и низкой стоимостью квадратного метра. Эти данные можно найти по имени в столбце locality_name.¶
Часть 5 Выделить квартиры в Санкт-Петербурге с помощью столбца locality_name и вычислить среднюю цену каждого километра. Описать, как стоимость объектов зависит от расстояния до центра города.¶

Шаг 5 Сделать общий вывод.¶

  • описать аномалии
  • замеченные зависимости

Шаг 0. Загрузка библиотек¶

Подключение необходимых библиотек осуществляю с применением инструкции Python import указывая ключевое слово import и имя нужной мне библиотеки. Также использую ключевое слово as для присвоения короткого псевдонима

In [1]:
# import libraries
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

Шаг 1. Открыть файл с данными и изучить общую информацию¶

Данные в формате csv загружаю с использованием функции read_csv() библиотеки pandas, в которую передаю путь к данным. Результат выполнения функции помещаю в переменную data

In [2]:
data = pd.read_csv('real_estate_data.csv', sep='\t')

Вывожу на экран общую информацию о файле

In [3]:
data.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 23699 entries, 0 to 23698
Data columns (total 22 columns):
 #   Column                Non-Null Count  Dtype  
---  ------                --------------  -----  
 0   total_images          23699 non-null  int64  
 1   last_price            23699 non-null  float64
 2   total_area            23699 non-null  float64
 3   first_day_exposition  23699 non-null  object 
 4   rooms                 23699 non-null  int64  
 5   ceiling_height        14504 non-null  float64
 6   floors_total          23613 non-null  float64
 7   living_area           21796 non-null  float64
 8   floor                 23699 non-null  int64  
 9   is_apartment          2775 non-null   object 
 10  studio                23699 non-null  bool   
 11  open_plan             23699 non-null  bool   
 12  kitchen_area          21421 non-null  float64
 13  balcony               12180 non-null  float64
 14  locality_name         23650 non-null  object 
 15  airports_nearest      18157 non-null  float64
 16  cityCenters_nearest   18180 non-null  float64
 17  parks_around3000      18181 non-null  float64
 18  parks_nearest         8079 non-null   float64
 19  ponds_around3000      18181 non-null  float64
 20  ponds_nearest         9110 non-null   float64
 21  days_exposition       20518 non-null  float64
dtypes: bool(2), float64(14), int64(3), object(3)
memory usage: 3.7+ MB

Загруженная таблица состоит из 22 колонок и 23699 строк. В колонках данные различных типов. Детальнее каждый столбец изучу в процессе исследования. Сейчас могу отметить, что есть пропуски в данных, их много и необходимо с этим разобраться. Присутствуют как непрерывные данные, так и категориальные. Есть данные, которые требуют изменения типа - first_day_exposition (дата публикации объявления) имеет тип данных object, необходимо преобразовать в тип date. Также имеет смысл в столбцах, где данные явно челочисленные, изменить тип данных с float64 на int64, что позволит исользовать их как категориальные при необходимости (количество этажей, балконов, парков и водоемов поблизости).

Смотрю в первом приближении распределение значений в стобцах с помощью частотной гистограммы

In [4]:
data.hist(figsize=(15, 20), edgecolor='black');

В столбцах с непрерывными данными вижу явные аномалии - сильное смещение графиков влево. Это значит, что присутствуют в небольшом количестве данные с очень высокими значениями. Изучу их в процессе предобработки и исследования. Также по графикам видно, что по количеству значений в столбцах лидируют объекты в пятиэтажных зданиях. Любопытно, проверю их долю от общего количества

In [5]:
amount_five_floor = len(data.query('floors_total == 5'))
In [6]:
display(f'Доля объектов в пятиэтажных зданиях - \
{round(amount_five_floor/ len(data) * 100, 2)} процента от общего количества')
'Доля объектов в пятиэтажных зданиях - 24.42 процента от общего количества'

Вывожу на экран первые пять строк загруженной таблицы

In [7]:
display(data.head(5))
total_images last_price total_area first_day_exposition rooms ceiling_height floors_total living_area floor is_apartment ... kitchen_area balcony locality_name airports_nearest cityCenters_nearest parks_around3000 parks_nearest ponds_around3000 ponds_nearest days_exposition
0 20 13000000.0 108.0 2019-03-07T00:00:00 3 2.70 16.0 51.0 8 NaN ... 25.0 NaN Санкт-Петербург 18863.0 16028.0 1.0 482.0 2.0 755.0 NaN
1 7 3350000.0 40.4 2018-12-04T00:00:00 1 NaN 11.0 18.6 1 NaN ... 11.0 2.0 посёлок Шушары 12817.0 18603.0 0.0 NaN 0.0 NaN 81.0
2 10 5196000.0 56.0 2015-08-20T00:00:00 2 NaN 5.0 34.3 4 NaN ... 8.3 0.0 Санкт-Петербург 21741.0 13933.0 1.0 90.0 2.0 574.0 558.0
3 0 64900000.0 159.0 2015-07-24T00:00:00 3 NaN 14.0 NaN 9 NaN ... NaN 0.0 Санкт-Петербург 28098.0 6800.0 2.0 84.0 3.0 234.0 424.0
4 2 10000000.0 100.0 2018-06-19T00:00:00 2 3.03 14.0 32.0 13 NaN ... 41.0 NaN Санкт-Петербург 31856.0 8098.0 2.0 112.0 1.0 48.0 121.0

5 rows × 22 columns

В столбцах есть назаполненные данные. Об этом говорят значения NaN (Not a Value)

Промежуточный вывод¶

При первичном изучении видно, что в представленном датасете есть пропуски (NaN) в данных. Есть странно высокие значения по столбцам с площадями квартир, жилой площади и кухни, а также аномально выглядят количество этажей в зданиях более 40, количество комнат 10 и более, высота потолков ниже 2м и выше 5м. Также имеет смысл исправить типы данных: в столбцах, где данные содержат количество неделимых объектов этажей, балконов, парков, водоемов, дней экспозиции лучше использовать целые числа. Столбец, содержащий информацию апартаменты или жилое помещение, предпочтительнее булевый тип данных, столбец с датой публикации соответственно должен иметь тип данных datetime

Шаг 2. Предобработка данных¶

Определить, в каких столбцах есть пропуски, по возможности устранить¶

Смотрю количество пропусков по столбцам с помощью функций isna() и sum()

In [8]:
data.isna().sum()
Out[8]:
total_images                0
last_price                  0
total_area                  0
first_day_exposition        0
rooms                       0
ceiling_height           9195
floors_total               86
living_area              1903
floor                       0
is_apartment            20924
studio                      0
open_plan                   0
kitchen_area             2278
balcony                 11519
locality_name              49
airports_nearest         5542
cityCenters_nearest      5519
parks_around3000         5518
parks_nearest           15620
ponds_around3000         5518
ponds_nearest           14589
days_exposition          3181
dtype: int64

Столбец 'locality_name' - Место расположения¶

Ввиду того, что для объектов недвижимости первым по значимости параметром является место расположения, то оставлять в столбце 'locality_name' пропуски не целесообразно. Можно заполнить их пустой строкой, но полагаю возможным строки, в которых не указан адрес, из исследования исключить. Их всего 49 из 23699, поэтому данное действие не окажет существенного влияния на результат исследования:

In [9]:
data = data.dropna(subset=['locality_name'])

Столбец 'floors_total' - Количество этажей¶

Аналогично из исследования можно исключить строки c пропусками в столбце 'floors_total'. Можно попробовать заполнить их наиболее часто встречающимися значениями, опираясь на номер этажа из столбца 'floor'. Однако, учитывая наличие всего 86 пропусков из 23699, отуствие этих строк не повлияет на результат:

In [10]:
data = data.dropna(subset=['floors_total'])

Столбец 'ceiling_height' - Высота потолков¶

Смотрю столбец с данными о высоте потолков

In [11]:
data.ceiling_height.describe()
Out[11]:
count    14481.000000
mean         2.771283
std          1.261983
min          1.000000
25%          2.510000
50%          2.650000
75%          2.800000
max        100.000000
Name: ceiling_height, dtype: float64

Вижу, что 50% значений находится в диапазоне от 2.5 м до 2.8 м. Среднее значение будет 2.77. Однако, предварительно выявлено, что почти четверть всех зданий это пятиэтажки, большая часть которых были построены в 50-70-е годы. Это типовые серии, в которых высота потолков была в диапазоне 2.5 - 2.65 м. Поэтому пропуски целесообразнее заполнить медианным значением 2.65м:

In [12]:
data['ceiling_height'] = data['ceiling_height'].fillna(data['ceiling_height'].median())

Столбцы 'living_area' - жилая площадь и 'kitchen_area' - площадь кухни¶

Данные о жилой площади зависят от планировки и количества комнат. Поэтому логично найти медианное значение жилой площади в каждой группе квартир и заполнить им соответствующие пропуски. Такой же подход можно использовать и при заполнении пропусков значений площади кухни. При заполнении добавляю проверку, чтобы сумма площади кухни и жилой площади не превышала общую площадь квартиры:

In [13]:
# filling gaps in column `living_area` with median
for r in data['rooms'].unique():
    data.loc[(data['rooms'] == r) & (data['living_area'].isna()), 'living_area'] = \
    data.groupby('rooms')['living_area'].median()[r]
In [14]:
# filling gaps in column `kitchen_area` with median
for r in data['rooms'].unique():
    data.loc[(data['rooms'] == r) & (data['kitchen_area'].isna()), 'kitchen_area'] =\
    data.groupby('rooms')['kitchen_area'].median()[r]
data.loc[(data['rooms'] == 0) & (data['kitchen_area'].isna()),
          'kitchen_area'] = data.fillna(0)

Задаю фукцию для проверки превышения общей площади. Если такое превышение выявлено, задаю понижающий коэффицинет к площадям комнаты и кухни:

In [15]:
# check total area
def check_area(x):
    """
    Сhecks the values according to the condition and adjusts them
    parameters:
        x - dataset row for checking;
    returns:
        dataset row with modified values by condition
    """
    k = x.loc['kitchen_area']
    l = x.loc['living_area']
    t = x.loc['total_area']
    if (k + l) > t:
        k = 0.8 * k
        l = 0.8 * l
        x.loc['kitchen_area'] = k
        x.loc['living_area'] = l
    return x

Применяю функцию к датасету

In [16]:
data = data.apply(check_area, axis=1)

Столбец 'is_apartment'¶

В столбце, который показывает, является ли объект аппартаментами, вероятно пропуски говорят о том, что это жилые квартиры. Исходя из этого, отсуствющие значения заполняю False

In [17]:
data['is_apartment'] = data['is_apartment'].fillna(value=False)

Столбец 'balcony'¶

В столбце, где указано количество балконов, пропуск значений с высокой долей вероятности означает отсутствие балконов, поэтому заполняю пропуски нулями - 0

In [18]:
data['balcony'] = data['balcony'].fillna(0)

Столбец 'days_exposition' количество дней размещения объявления с момента публикации до снятия¶

Возможная причина пропусков данных в столбце с периодом экспозиции состоит в том, что если объявление еще не снято, то период на высчитан, соответственно значения в этом столбце нет. Оставлю как есть. В дальнейшем, если в ходе исследования данные пропуски будут критичны, приму меры к их устранению

Столбцы с картографическими данными 'airports_nearest', 'cityCenters_nearest', 'ponds_nearest', 'parks_nearest'¶

Смотрю столбцы, которые связаны с картографическими данными: расстояния до парков, водоемов, центра города и аэропорта.

In [19]:
data[['airports_nearest', 'cityCenters_nearest', 'ponds_nearest', 'parks_nearest']].head(10)
Out[19]:
airports_nearest cityCenters_nearest ponds_nearest parks_nearest
0 18863.0 16028.0 755.0 482.0
1 12817.0 18603.0 NaN NaN
2 21741.0 13933.0 574.0 90.0
3 28098.0 6800.0 234.0 84.0
4 31856.0 8098.0 48.0 112.0
5 NaN NaN NaN NaN
6 52996.0 19143.0 NaN NaN
7 23982.0 11634.0 NaN NaN
8 NaN NaN NaN NaN
9 50898.0 15008.0 NaN NaN

По картографическим данным можно заметить, что в данных о расстояниях значения отсуствуют по одному и тому же объекту в нескольких колонках. Возможно причина этого, отсутствие каких-либо настроек при автоматическом заполнении картографических данных, связанных с определением места расположения объекта.

Учитывая тот факт, что в датасете из адреса объекта известно только наименование населенного пункта без указания улицы и номера дома, точное определение расстояний невозможно. Заполнение усредненными значениями или медианными также очень далеко от реальности, так как размеры города Санкт - Петербурга таковы, что расстояния до аэропорта могут отличаться в несколько раз у объектов из одного города. Оставляю пока эти данные как есть. Если в процессе исследования будут вопросы, затрагивающие эти данные, буду принимать решение, как избавляться от этих пропусков

Столбцы с картографическими данными 'parks_around3000', 'parks_around3000'¶

В отношении заполнения пропусков значений в столбцах количество парков и водоемов в близости от объекта также не представляется возможным без знания точного адреса. Пока оставлю как есть. В исследовательском анализе приму решение, если необходимо будет ликвидировать пропуски для достижения целей исследования

Проверить дубликаты¶

Проверяю наличие явных дубликатов строк

In [20]:
data.duplicated().sum()
Out[20]:
0

Явных дубликатов нет

Изучить типы данных, при необходимости преобразовать¶

Для корректного проведения различных операций со столбцами, в том числе математических приведу типы данных в столбцах к более подходящим типам. Например, столбцы с данными, в которых есть целые числа и дробные числа, должны иметь соответстующий тип данных 'int' и 'float'. Столбцы со значениями True и False должны иметь тип данных 'bool'. Столбцы, в которых содержатся текстовые данные могут иметь тип 'object', а содержащие даты - 'datetime'. Это позволит использовать соответствующие функции Python для работы с этими дынными целиком по столбцам. После приведения к нужным типам датасет будет готов к дальнейшей работе.

In [21]:
for i in ['floors_total', 'balcony', 'parks_around3000', 'ponds_around3000', 'days_exposition']:
    data[i] = np.floor(pd.to_numeric(data[i], errors='coerce')).astype('Int64')
In [22]:
data['first_day_exposition'] = pd.to_datetime(data['first_day_exposition'])
In [23]:
data.info()
<class 'pandas.core.frame.DataFrame'>
Index: 23565 entries, 0 to 23698
Data columns (total 22 columns):
 #   Column                Non-Null Count  Dtype         
---  ------                --------------  -----         
 0   total_images          23565 non-null  int64         
 1   last_price            23565 non-null  float64       
 2   total_area            23565 non-null  float64       
 3   first_day_exposition  23565 non-null  datetime64[ns]
 4   rooms                 23565 non-null  int64         
 5   ceiling_height        23565 non-null  float64       
 6   floors_total          23565 non-null  Int64         
 7   living_area           23565 non-null  float64       
 8   floor                 23565 non-null  int64         
 9   is_apartment          23565 non-null  bool          
 10  studio                23565 non-null  bool          
 11  open_plan             23565 non-null  bool          
 12  kitchen_area          23565 non-null  float64       
 13  balcony               23565 non-null  Int64         
 14  locality_name         23565 non-null  object        
 15  airports_nearest      18041 non-null  float64       
 16  cityCenters_nearest   18064 non-null  float64       
 17  parks_around3000      18065 non-null  Int64         
 18  parks_nearest         8030 non-null   float64       
 19  ponds_around3000      18065 non-null  Int64         
 20  ponds_nearest         9036 non-null   float64       
 21  days_exposition       20394 non-null  Int64         
dtypes: Int64(5), bool(3), datetime64[ns](1), float64(9), int64(3), object(1)
memory usage: 4.3+ MB

Удаление аномальных значений¶

На предыдущем этапе графики гистограммы - частоты распределения значений, показали, что есть необычно высокие значения в отдельных столбцах. Для поиска аномалий, изучу каждый из столбцов 'last_price', 'total_area', 'ceiling_height', 'floors_total' отдельно

Столбец 'floors_total' - количество этажей¶

Смотрю уникальные значения в столбце

In [24]:
data['floors_total'].sort_values().unique()
Out[24]:
<IntegerArray>
[ 1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 33, 34, 35, 36, 37, 52, 60]
Length: 36, dtype: Int64

Согласно ТОП-10 самых высоких зданий Санкт-Петербурге, здесь нет жилых зданий высотой более 37 этажей. В нашем случае это две квартиры, одна из которых находится в Кронштадте на 4-м этаже и видимо опечатка, не 60, а 6 этажей. Вторая на 18-м этаже, 52-х этажного здания. Здесь также возможна опечатка. Возможно имели ввиду 22 или 32 этажа. Вероятность, что 22 выше, оставляю это значение.

In [25]:
data[data['floors_total'] > 37]
Out[25]:
total_images last_price total_area first_day_exposition rooms ceiling_height floors_total living_area floor is_apartment ... kitchen_area balcony locality_name airports_nearest cityCenters_nearest parks_around3000 parks_nearest ponds_around3000 ponds_nearest days_exposition
2253 12 3800000.0 45.5 2018-06-28 2 2.88 60 27.4 4 False ... 7.4 0 Кронштадт 67763.0 49488.0 2 342.0 3 614.0 166
16731 9 3978000.0 40.0 2018-09-24 1 2.65 52 10.5 18 False ... 14.0 0 Санкт-Петербург 20728.0 12978.0 1 793.0 0 NaN 45

2 rows × 22 columns

In [26]:
data.at[2253, 'floors_total'] = 6
data.at[16731, 'floors_total'] = 22

Проверяю. Теперь порядок.

In [27]:
data[data['floors_total'] > 37]
Out[27]:
total_images last_price total_area first_day_exposition rooms ceiling_height floors_total living_area floor is_apartment ... kitchen_area balcony locality_name airports_nearest cityCenters_nearest parks_around3000 parks_nearest ponds_around3000 ponds_nearest days_exposition

0 rows × 22 columns

Зданий выше 37 этажей в Санкт-Петербурге нет. Аномальные значения выше 37 удалены.

Столбец 'last_price' - цена на момент снятия с публикации¶

In [28]:
pd.options.display.float_format ='{:,.2f}'.format
In [29]:
data.last_price.describe()
Out[29]:
count        23,565.00
mean      6,540,058.26
std      10,910,934.72
min          12,190.00
25%       3,400,000.00
50%       4,646,000.00
75%       6,790,000.00
max     763,000,000.00
Name: last_price, dtype: float64

Вижу, что среднее значение цены 6,5 млн.руб. И стандартное отклонение составляет 10,9 млн. Вижу также, что максимальное значение цены 763 млн.руб. слишком высоко и влияет на среднюю цену. В нашем случае это значение явная аномалия. Поэтому посмотрю, какая цена является 99 перцентилем, то есть какая максимальная цена у 99 процентов объектов, исключая те, которые дороже.

In [30]:
np.percentile(data.last_price, 99)
Out[30]:
36000000.0
In [31]:
data[data['last_price'] > 36000000].value_counts().sum()
Out[31]:
105

Цена выше 36 милионов y 105 объектoв, что составляет менее одного процента от общего их количества, поэтому ограничу исследование диапазоном цен от 0 до 36 млн.рублей.

Делаю срез данных по объектам, цена которых не превышает 36 млн.рублей.

In [32]:
data = data[data.last_price <= 36000000]

В ценах объектов недвижимости есть объекты с аномально высокой ценой с точки зрения настоящего исследования. Оставляю для дальнейшего исследования объекты стоимостью до 36 млн.руб. включительно, которые составляют 99 всего количества. Объекты с более высокой ценой отбросил как аномальные

Cтолбец 'total_area' - общая площадь¶

In [33]:
data.total_area.describe()
Out[33]:
count   23,330.00
mean        58.65
std         29.41
min         12.00
25%         40.00
50%         51.70
75%         68.72
max        470.30
Name: total_area, dtype: float64

Похожая ситуация как и с ценами. Посмотрю 99 перцентиль по общей площади

In [34]:
np.percentile(data.total_area, 99)
Out[34]:
170.0
In [35]:
data[data['total_area'] > 175].value_counts().sum()
Out[35]:
65

99 процентов значений общей площади лежат в диапазоне до 175 квадратных метров. Оставшиеся 65 объектов имеют площадь более 175 квадратных метров. Удаляю их как аномальные

In [36]:
data = data[data['total_area'] <= 175]

Столбец 'ceiling_height' - высота потолка¶

Смотрю уникальные значения высоты потолков в столбце

In [37]:
data.ceiling_height.sort_values().unique()
Out[37]:
array([  1.  ,   1.2 ,   1.75,   2.  ,   2.2 ,   2.25,   2.3 ,   2.34,
         2.4 ,   2.45,   2.46,   2.47,   2.48,   2.49,   2.5 ,   2.51,
         2.52,   2.53,   2.54,   2.55,   2.56,   2.57,   2.58,   2.59,
         2.6 ,   2.61,   2.62,   2.63,   2.64,   2.65,   2.66,   2.67,
         2.68,   2.69,   2.7 ,   2.71,   2.72,   2.73,   2.74,   2.75,
         2.76,   2.77,   2.78,   2.79,   2.8 ,   2.81,   2.82,   2.83,
         2.84,   2.85,   2.86,   2.87,   2.88,   2.89,   2.9 ,   2.91,
         2.92,   2.93,   2.94,   2.95,   2.96,   2.97,   2.98,   2.99,
         3.  ,   3.01,   3.02,   3.03,   3.04,   3.05,   3.06,   3.07,
         3.08,   3.09,   3.1 ,   3.11,   3.12,   3.13,   3.14,   3.15,
         3.16,   3.17,   3.18,   3.2 ,   3.21,   3.22,   3.23,   3.24,
         3.25,   3.26,   3.27,   3.28,   3.29,   3.3 ,   3.31,   3.32,
         3.33,   3.34,   3.35,   3.36,   3.37,   3.38,   3.39,   3.4 ,
         3.42,   3.44,   3.45,   3.46,   3.47,   3.48,   3.49,   3.5 ,
         3.51,   3.52,   3.53,   3.54,   3.55,   3.56,   3.57,   3.58,
         3.59,   3.6 ,   3.62,   3.63,   3.65,   3.66,   3.67,   3.68,
         3.7 ,   3.75,   3.78,   3.8 ,   3.83,   3.84,   3.85,   3.88,
         3.9 ,   3.93,   3.95,   3.98,   4.  ,   4.06,   4.1 ,   4.14,
         4.15,   4.19,   4.2 ,   4.25,   4.3 ,   4.37,   4.4 ,   4.5 ,
         4.7 ,   4.8 ,   5.  ,   5.3 ,   5.8 ,   8.  ,   8.3 ,  10.3 ,
        14.  ,  20.  ,  22.6 ,  24.  ,  25.  ,  26.  ,  27.  ,  27.5 ,
        32.  , 100.  ])

В Санкт-Петербурге есть исторические здания, в которых высота потолков превышает 4 метра, также могут быть и современные элитные квартиры и апартаменты с аналогичыми показателями. Количество таких объектов невелико, учитывавая среднее значение высоты потолка в 2.77 м. По действующим нормам минимальная высота потоков не должна быть ниже 2.5, однако, есть на рынке помещения, в которых данный стандарт не соблюден или они были построены до его принятия. Также в датасете есть апартаменты, которые могут и не выдерживать нормы для жилых помещений - например, расположенные в цокольных этажах. В данном случае принимаю решение оставить строки с высотой потолков от 2.2м до 5м. При этом есть двузначные значения, которые могут быть опечатками и нужно перенести десятичную точку на один знак, отфильтровать значения 2.2 м и ниже, 5 м и выше, обновить основной датасет:

In [38]:
height_more_five_change = data.query('ceiling_height >= 5').copy()
In [39]:
height_more_five_change['ceiling_height'] = height_more_five_change['ceiling_height'] * 0.1
In [40]:
data.loc[data['ceiling_height'] >= 5, 'ceiling_height'] = height_more_five_change['ceiling_height']

Срезаю объекты с потолками выше 5 метров

In [41]:
data = data[data['ceiling_height'] < 5]

Срезаю объекты с потолками ниже 2.5 метра

In [42]:
data = data[data['ceiling_height'] > 2.2]

Промежуточный вывод¶

  • В датасете обнаружены пропущенные значения. В тех столбцах, где количество пропусков незначительно, соответствующие строки удалены (название населенного пункта, количество этажей). Часть пропусков заполнены медианными значениями (жилая площадь,площадь кухни с корректировкой относительно общей, высота потолков), часть 0 (количество балконов) и значением False в столбце, определяющем является ли объект апартаментами. В столбцах с картографическими данными пропуски также можно было заполнить применяя один из методов, однако принял решение пока их оставить как есть ввиду низкой достоверности заполняемых значений.

  • Проверка на дубликаты явных дубликатов не выявила

  • Изучены типы данных, в отдельных столбцах приведены к необходимым для исследования типам 'int', 'datetime'

  • Выявлены аномальные значения в ряде столбцов и удалены

Шаг 3. Посчитать и добавить в таблицу новые столбцы¶

Цена одного квадратного метра 'price_per_meter'¶

In [43]:
data['price_per_meter'] = data['last_price'] / data['total_area']
data['price_per_meter'] = data['price_per_meter'].round(2)

День недели публикации 'first_exp_weekday'¶

In [44]:
data['first_exp_weekday'] = data['first_day_exposition'].dt.weekday

Месяц публикации объявления 'first_exp_month'¶

In [45]:
data['first_exp_month'] = data['first_day_exposition'].dt.month

Год публикации объявления 'first_exp_year'¶

In [46]:
data['first_exp_year'] = data['first_day_exposition'].dt.year

Тип этажа квартиры (значения "первый", "последний", "другой") 'type_floor'¶

In [47]:
change_a = (data['floor'] == 1).to_list()
change_b = (data['floor'] == data['floors_total']).to_list()
change_c = ((data['floor'] != data['floors_total']) & (data['floor'] != 1)).to_list() 

condlist = [change_a, change_b, change_c]
choices = ['первый', 'последний', 'другой']

data['type_floor'] = np.select(condlist, choices)

Расстояние до центра города в километрах 'cityCenters_nearest_km'¶

In [48]:
data['cityCenters_nearest_km'] = data['cityCenters_nearest'] / 1000
data['cityCenters_nearest_km'] = data['cityCenters_nearest_km'].round()

Шаг 4. Провести исследовательский анализ данных¶

Часть 1. Исследование столбцов с данными. Предварительные выводы¶

In [49]:
%config InlineBackend.figure_format = 'retina' # improves visualisation

Общая площадь 'total_area'¶

In [50]:
# histogram `total_area`
data.total_area.plot(kind='hist', bins=180, grid=True, figsize=(10,4), edgecolor='black',
                         title="Распределение значений общей площади",
                         xlabel="Общая площадь (кв.м)",
                         ylabel="Количество значений"
                        );
In [51]:
data.total_area.describe()
Out[51]:
count   23,098.00
mean        57.18
std         24.42
min         12.00
25%         40.00
50%         51.20
75%         68.00
max        175.00
Name: total_area, dtype: float64

Вывод: Вижу по графику, что значения площадей распределились по группам. И основной объем приходится на квартиры до 40 квадратных метров и от 40 до 60 квадратны метров. Видимо это одно- и двухкомнатные квартиры. Среднее значение общей площади квартиры 57.2 кв.м, медианное значение - 51.2 кв.м

Жилая площадь 'living_area'¶

In [52]:
# histogram `living_area`
data.living_area.plot(kind='hist', bins=140, grid=True, figsize=(10,4), edgecolor='black',
                         title="Распределение значений жилой площади",
                         xlabel="Жилая площадь (кв.м)",
                         ylabel="Количество значений"
                        );
In [53]:
data.living_area.describe()
Out[53]:
count   23,098.00
mean        32.56
std         16.15
min          2.00
25%         18.20
50%         30.00
75%         42.00
max        140.00
Name: living_area, dtype: float64

Вывод: На диаграмме явно прослеживаются три пика. Это очевидно одно, двух и трехкомнатные квартиры. И наибольшее предложение в сегменте однокомнатных квартир с площадью комнаты 16-18 квадратных метров. Среднее значение жилой площади 32.56 кв.м, медианное 30 кв.м

Площадь кухни 'kitchen_area'¶

In [54]:
# histogram `kitchen_area`
data.kitchen_area.plot(kind='hist', bins=100, grid=True, figsize=(10,4), edgecolor='black',
                         title="Распределение значений площади кухни",
                         xlabel="Площадь кухни (кв.м)",
                         ylabel="Количество значений"
                        );
In [55]:
data.kitchen_area.describe()
Out[55]:
count   23,098.00
mean        10.04
std          4.84
min          0.00
25%          7.10
50%          9.00
75%         11.20
max         65.00
Name: kitchen_area, dtype: float64

Вывод: На диаграмме явно прослеживаются несколько групп квартир: с площадью кухни около 6-7 квадратных метров - это стандартные серии домов 60-х - 70-х годов прошлого века и большая часть ранее построенных зданий, в диапазоне 8-9 квадратных метров - это улучшенные серии 80-х-начала 90-х годов и на третьем месте кухни площадью 10 квадратных метров и более - это уже здания с середины 90-х годов по настоящее время. Распределение вполне адекватно отражает особенности существующего жилого фонда. Нулевые значения площадей кухни - в студиях и возможно апартаментах.

Цена объекта 'last_price'¶

Изменю масштаб значений цены на млн.руб. для удобства отображения.

In [56]:
data['last_price'] = data['last_price'] / 1000000
In [57]:
# histogram `last_price`
data.last_price.plot(kind='hist', bins=100, grid=True, figsize=(10,4), edgecolor='black',
                         title="Распределение значений цены объекта",
                         xlabel="Цена (млн.руб)",
                         ylabel="Количество значений"
                        );
In [58]:
data.last_price.describe()
Out[58]:
count   23,098.00
mean         5.69
std          4.01
min          0.01
25%          3.40
50%          4.60
75%          6.60
max         36.00
Name: last_price, dtype: float64

Вывод: Большинство предлагаемых к продаже объектов собственники оценивают в диапазоне от 3 до 7 млн. рублей, средняя цена предложения 5.69 млн.руб., медианное значение цены - 4.6 млн.руб.

Количество комнат 'rooms'¶

In [59]:
# histogram `rooms`
data.rooms.plot(kind='hist', bins=20, grid=True, figsize=(6,4), edgecolor='black',
                         title="Распределение значений количества комнат",
                         xlabel="Количество комнат (шт.)",
                         ylabel="Количество значений"
                        );

Смотрю точное количество объектов с количеством комнат в абсолютных и относительных величинах

In [60]:
data.rooms.value_counts()
Out[60]:
rooms
1    7986
2    7875
3    5688
4    1075
5     225
0     192
6      42
7      14
8       1
Name: count, dtype: int64
In [61]:
data.rooms.value_counts(normalize=True)
Out[61]:
rooms
1   0.35
2   0.34
3   0.25
4   0.05
5   0.01
0   0.01
6   0.00
7   0.00
8   0.00
Name: proportion, dtype: float64

Вывод:¶

Продаваемые объекты имеют в основном одну - 35% или две комнаты - 34%, доля трехкомнатных - 25%. Таким образом, эти три типа планировок составляют 94% всех предложений о продажу. Есть нулевые значения комнат. Вполне возможно, они могли появиться при заполнении объявлений по студиям

Высота потолков 'ceiling_height'¶

In [62]:
# histogram `ceiling_height`
data.ceiling_height.plot(kind='hist', bins=50, grid=True, figsize=(6,4), edgecolor='black',
                         title="Распределение значений высоты потолков",
                         xlabel="Высота потолков (метры)",
                         ylabel="Количество значений"
                        );
In [63]:
data.ceiling_height.describe()
Out[63]:
count   23,098.00
mean         2.69
std          0.20
min          2.25
25%          2.60
50%          2.65
75%          2.70
max          4.80
Name: ceiling_height, dtype: float64

Вывод: Распределение квартир по высоте потолков напоминают графики распределения квартир по общей площади и площади кухни - похожие два пика с большим количеством значений. Это закономерно, так как высота потолка, как и общая площадь, площадь кухни зависят от проекта дома и года постройки. Исторически в Санкт-Петербурге существуют районы массовой комплексной застройки, где находятся здания типовых серий 60-х - 70-х годов прошлого века со стандартными потолками 2.5м. Более старые здания попали в другой диапазон: здания до 60-х годов, как и здания после 70-х имеют более высокие потолки от 2.6 до 3 метров

Этаж квартиры 'floor'¶

In [64]:
# histogram `floor`
data.floor.plot(kind='hist', bins=74, grid=True, figsize=(8,4), edgecolor='black',
                         title="Распределение квартир по этажам",
                         xlabel="Этаж (номер)",
                         ylabel="Количество значений"
                        );
In [65]:
data.floor.value_counts()
Out[65]:
floor
2     3282
3     2990
1     2877
4     2709
5     2542
6     1262
7     1177
8     1055
9     1037
10     677
11     515
12     512
13     374
15     336
14     329
16     308
17     222
18     173
19     141
21     119
22     111
20     108
23      98
24      60
25      44
26      24
27      10
28       1
30       1
29       1
32       1
33       1
31       1
Name: count, dtype: int64

Вывод: Распределение по этажам также повторяет вышеописанные наблюдения о составе жилого фонда. Очевидно, что большая часть предложений о продаже в пятиэтажных зданиях. Больше всего (в сравнении с каждой другой категорией отдельно) предложений на вторых этажах.

Тип этажа квартиры («первый», «последний», «другой») 'type_floor'¶

In [66]:
data.type_floor.value_counts()
Out[66]:
type_floor
другой       17012
последний     3209
первый        2877
Name: count, dtype: int64
In [67]:
data.type_floor.value_counts(normalize=True)
Out[67]:
type_floor
другой      0.74
последний   0.14
первый      0.12
Name: proportion, dtype: float64

Вывод: На последних этажах продается 14%, а на первых 12% от всех объектов, а вместе они составляют 26 процентов от всего объема предложения

Общее количество этажей в доме 'floors_total'¶

In [68]:
# histogram `floors_total`
data.floors_total.plot(kind='hist', bins=30, grid=True, figsize=(6,4), edgecolor='black',
                         title="Распределение значений этажности зданий",
                         xlabel="Количество этажей (шт)",
                         ylabel="Количество значений"
                        );

Вывод: Диаграмма подтверждает выше сделанные наблюдения: самая большая группа предложений в пятиэтажных домах, второе место - девятиэтажные. Это в основной массе здания типовой жилой застройки 60-х - 80-х годов прошлого века

Расстояние до центра города 'cityCenters_nearest' Санкт-Петербурга¶

In [69]:
data['cityCenters_nearest'] = data['cityCenters_nearest'] / 1000
In [70]:
# histogram `cityCenters_nearest`
data.cityCenters_nearest.plot(kind='hist', bins=150, grid=True, figsize=(10,4), edgecolor='grey',
                         title="Распределение значений расстояний до центра города",
                         xlabel="Расстояние до центра города (км)",
                         ylabel="Количество значений"
                        );
In [71]:
data.cityCenters_nearest.describe()
Out[71]:
count   17,622.00
mean        14.37
std          8.58
min          0.18
25%          9.59
50%         13.22
75%         16.38
max         65.97
Name: cityCenters_nearest, dtype: float64

Вывод: Большая часть объектов из выборки расположены на расстоянии примерно от 7 до 17 километров от центра города. Среднее расстояние 14.37км, медианное значение расстояния 13.22км

Расстояние до ближайшего аэропорта 'airports_nearest'¶

In [72]:
data['airports_nearest'] = data['airports_nearest'] / 1000
In [73]:
# histogram `airports_nearest`
data.airports_nearest.plot(kind='hist', bins=150, grid=True, figsize=(10,4), edgecolor='grey',
                         title='Распределение значений расстояний до аэропорта "Пулково"',
                         xlabel="Расстояние до аэропорта (км)",
                         ylabel="Количество значений"
                        );
In [74]:
data.airports_nearest.describe()
Out[74]:
count   17,602.00
mean        28.84
std         12.73
min          0.00
25%         18.45
50%         26.88
75%         37.41
max         84.87
Name: airports_nearest, dtype: float64

Вывод: Большая часть объектов из выборки расположены на расстоянии от 10 до 40 километров от аэропорта "Пулково". Среднее расстояние 28.84км, медианное значение расстояния 26.88км

Расстояние до ближайшего парка 'parks_nearest'¶

In [75]:
# histogram `parks_nearest`
data.parks_nearest.plot(kind='hist', bins=150, grid=True, figsize=(10,4), edgecolor='grey',
                         title='Распределение значений расстояний до ближайшего парка',
                         xlabel="Расстояние до ближайшего парка (м)",
                         ylabel="Количество значений"
                        );

Вывод: Подавляющее количество квартир расположено на расстоянии не более одного километра от ближайшего парка. Санкт-Петербург - зеленый город.

Расстояние до ближайшего водоема 'ponds_nearest'¶

In [76]:
# histogram `ponds_nearest`
data.ponds_nearest.plot(kind='hist', bins=150, grid=True, figsize=(10,4), edgecolor='grey',
                         title='Распределение значений расстояний до ближайшего водоема',
                         xlabel="Расстояние до ближайшего водоема (м)",
                         ylabel="Количество значений"
                        );

Вывод: Аналогично паркам водоемы расположены на расстоянии не более одного километра от большинства квартир.

День недели публикации объявления 'first_exp_weekday'¶

In [77]:
# histogram `first_exp_weekday`
fig, ax = plt.subplots()
ax.hist(data['first_exp_weekday'], bins = 25, rwidth = 0.6)
ax.set_title('День недели первой публикации объявления, 0 - понедельник')
ax.set_ylabel('Количество объявлений')
plt.show()

Вывод: Чаще объявления публиковались во вторник и четверг. Реже всего в воскресенье.

Месяц публикации объявления 'first_exp_month'¶

In [78]:
# # histogram `first_exp_month`
fig, ax = plt.subplots()
ax.hist(data['first_exp_month'], bins = 24, rwidth = 0.5)
ax.set_title('Месяц первой публикации объявления')
ax.set_ylabel('Количество объявлений')
plt.show()

Вывод: Самые активные по публикациям февраль, март, апрель и ноябрь. Самый "ленивый" для публикаций месяц май, за ним январь. Видимо из-за длинных выходных.

Часть 2. Изучить, как быстро продавались квартиры (days_exposition).¶

In [79]:
# histogram `days_exposition`
data.days_exposition.plot(kind='hist', bins=100, grid=True, figsize=(10,4), edgecolor='black',
                         title="Распределение объектов по количеству дней в продаже",
                         ylabel="Количество объявлений",
                         xlabel="Количество дней в продаже"
                        );
In [80]:
data.days_exposition.describe()
Out[80]:
count   20,035.00
mean       178.79
std        217.71
min          1.00
25%         44.00
50%         94.00
75%        228.00
max      1,580.00
Name: days_exposition, dtype: Float64

Вывод: Среднее значение по столбцу 177 дней. При этом половина всех квартира проданы за 94 дня и быстрее. Еще четверть квартир продавались от 94 до 228 дней. Быстрой можно считать продажу быстрее, чем за 44 дня. Слишком долгая экспозиция объекта - более 225 дней

Посмотрю дополнително как распределены значения внутри диапазона от 0 до 100 дней

In [81]:
# histogram `days_exposition` от 0 до 100 дней
data['days_exposition'][data['days_exposition'] < 100].plot(kind='hist', bins=100, 
                         grid=True, figsize=(10,4), edgecolor='black',
                         title="Распределение объектов по количеству дней в продаже",
                         ylabel="Количество объявлений",
                         xlabel="Количество дней в продаже"
                        );
In [82]:
data['days_exposition'][data['days_exposition'] < 100].describe()
Out[82]:
count   10,333.00
mean        44.62
std         26.69
min          1.00
25%         21.00
50%         45.00
75%         63.00
max         99.00
Name: days_exposition, dtype: Float64

Вывод: Есть пики на 45-м дне и на 60-м. Предполагаю, что это связано с автоматической настройкой платформы Яндекс.Недвижимость, где размещаются объявления. Видимо при определенных условиях, они автоматически публикуются или автоматически снимаются с рекламы.

Часть 3. Какие факторы больше всего влияют на общую (полную) стоимость объекта?¶

Изучy, зависит ли цена от:

  • общей площади;
  • жилой площади;
  • площади кухни;
  • количества комнат;
  • этажа, на котором расположена квартира (первый, последний, другой);
  • даты размещения (день недели, месяц, год).

Построю графики, которые покажут зависимость цены от указанных выше параметров

Зависимость между ценой и общей площадью¶

In [83]:
# scatter plot 'last_price' and 'total_area' 
x=data.last_price
y=data.total_area
plt.figure(figsize=(9, 5))
plt.scatter(x, y, s=20, alpha=0.3) 
slope, intercept = np.polyfit(x, y, 1)
plt.grid()
plt.title("Зависимость между ценой и общей площадью")
plt.xlabel("Цена в млн.руб.")
plt.ylabel("Площадь в кв.м.")
plt.plot(x, slope*x + intercept, color='red', linestyle='dotted', label='Линия тренда')
plt.legend()
plt.show()

Считаю коэффициент корреляции между значениями общей площади и цены

In [84]:
data['last_price'].corr(data['total_area'])
Out[84]:
0.771500307915153

Коэффициент положительный и его значение 0.77 достаточно близко к единице, что отражает сильную положительную взаимосвязь между ценой и общей площадью. При увеличении одного показателя увеличивается и другой

Зависимость между ценой и жилой площадью¶

In [85]:
# scatter plot 'last_price' and 'living_area' 
x=data.last_price
y=data.living_area
plt.figure(figsize=(9, 5))
plt.scatter(x, y, s=20, alpha=0.3) 
slope, intercept = np.polyfit(x, y, 1)
plt.grid()
plt.title("Зависимость между ценой и жилой площадью")
plt.xlabel("Цена в млн.руб.")
plt.ylabel("Площадь в кв.м.")
plt.plot(x, slope*x + intercept, color='red', linestyle='dotted', label='Линия тренда')
plt.legend()
plt.show()

Считаю коэффициент корреляции между значениями жилой площади и цены

In [86]:
data['last_price'].corr(data['living_area'])
Out[86]:
0.6319549666825636

Коэффициент положительный и его значение 0.63 говорит о наличии взаимосвязи между ценой и общей площадью. При увеличении одного показателя увеличивается и другой. Однако, в данном случае эта зависимость ниже, чем между ценой и общей площадью.

Зависимость между ценой и площадью кухни¶

In [87]:
# scatter plot 'last_price' and 'kitchen_area' 
x=data.last_price
y=data.kitchen_area
plt.figure(figsize=(9, 5))
plt.scatter(x, y, s=20, alpha=0.3) 
slope, intercept = np.polyfit(x, y, 1)
plt.grid()
plt.title("Зависимость между ценой и площадью кухни")
plt.xlabel("Цена в млн.руб.")
plt.ylabel("Площадь в кв.м.")
plt.plot(x, slope*x + intercept, color='red', linestyle='dotted', label='Линия тренда')
plt.legend()
plt.show()

Считаю коэффициент корреляции между значениями площади кухни и цены

In [88]:
data['last_price'].corr(data['kitchen_area'])
Out[88]:
0.5644287348572664

Картина та же, что и выше, но зависимость несколько меньше.

Зависимость между ценой и количеством комнат¶

In [89]:
data.groupby(['rooms'])['last_price'].agg('mean').round(2).plot(
    xlabel='количество комнат', ylabel='средняя цена', kind='bar')
plt.show()
In [90]:
data['last_price'].corr(data['rooms'])
Out[90]:
0.47360555025077206

Коэффициент корреляции положительный, чем больше комнат, тем выше цена

Зависимость между ценой и этажом, на котором расположена квартира (первый, последний, другой)¶

При сравнении цен по типам этажей, годам, месяцам и дням размешения фактически сравниваю соответствующие группы объектов. Так как объекты группируются по категориям, то к цене применяю агрегирующую функцию - средняя цена (mean)

In [91]:
data.groupby(['type_floor'])['last_price'].agg('mean').round(2).plot(
    xlabel='тип этажа', rot=45, ylabel='средняя цена', kind='bar' )
plt.show()

В среднем на первом этаже квартиры оцениваются дешевле, затем следуют последние этажи и лучше цены на квартиры, расположенные на других этажах

Чтобы посчитать корреляцию, привожу типы столбцов 'первый', 'последний', 'другой' к числовым значениям: 1, 2, 3

In [92]:
change_a = (data['type_floor'] == 'первый')
change_b = (data['type_floor'] == 'последний')
change_c = (data['type_floor'] == 'другой') 

conditions = [change_a, change_b, change_c]
choices = [1, 2, 3]

data['type_floor_num'] = np.select(conditions, choices)
In [93]:
data['last_price'].corr(data['type_floor_num'])
Out[93]:
0.12227561700714402

Коэффициент корреляции положительный, но не значительно выше 0, взаимосвязь есть, но невысокая

Зависимость между ценой и датой размещения объявления: годом, месяцем, днем¶

In [94]:
data.groupby(['first_exp_year'])['last_price'].agg('mean').round(2).plot(
    xlabel='год публикации', ylabel='средняя цена', kind='bar'
)
plt.title('Год первой публикации объявления')
plt.show();

Неожиданный результат. Оказывается средняя цена предложений снижалась несколько лет, а затем снова начался рост. Это отличается от общего мнения, что цены на недвижимость всегда растут

In [95]:
data.groupby(['first_exp_month'])['last_price'].agg('mean').round(2).plot(
    xlabel='месяц публикации', ylabel='средняя цена', kind='bar')
plt.title('Месяц первой публикации объявления, 1 - январь')
plt.show();

Самые высокие в среднем цены предложений в апреле и сентябре, самые низкие в июне

In [96]:
data.groupby(['first_exp_weekday'])['last_price'].agg('mean').round(2).plot(
    xlabel='день публикации',  ylabel='средняя цена', kind='bar')
plt.title('День недели первой публикации объявления, 0 - понедельник')
plt.show();

На неделе средняя цена предложения выше во вторник, самые низкие в субботу

Вывод:¶

Самое большое влияние на цену квартир оказывают количество комнат, а также параметры площади: общей, жилой, кухни. Из объективных факторов следует отметить год публикации объявления. Очевидно, что рыночная ситуация меняется от года к году и влияет на цены квартир и платежеспособный спрос

Часть 4.¶

Посчитать среднюю цену одного квадратного метра в 10 населённых пунктах с наибольшим числом объявлений. Выделить населённые пункты с самой высокой и низкой стоимостью квадратного метра.¶

Ищу 10 населенных пунктов с наибольшим числом объявлений

In [97]:
data['locality_name'].value_counts().head(10)
Out[97]:
locality_name
Санкт-Петербург      15221
посёлок Мурино         520
посёлок Шушары         439
Всеволожск             397
Пушкин                 363
Колпино                337
посёлок Парголово      326
Гатчина                307
деревня Кудрово        299
Выборг                 233
Name: count, dtype: int64
In [98]:
top_list = data['locality_name'].value_counts().head(10).index.to_list()
In [99]:
data_top_ten = data.loc[data['locality_name'].isin(top_list)].copy()

Вывожу стобчатую диаграмму для сравнения средних цен за квадратный метр в ТОП-10 населенных пунктах по количеству объявленй о продаже. Группирую данные по названиям населенных пунктов, агрегирующую функцию применяю - среднее значение

In [100]:
data_top_ten.groupby('locality_name')['price_per_meter'].agg('mean'
                    ).round(2).sort_values().plot(
    xlabel='Населенный пункт',  ylabel='Среднее значение цены (руб/кв.м)', kind='bar', rot=45)
plt.title('Среднее значение цены кв.м в ТОП-10 населенных пунктах по количеству объявлений')
plt.show();                                                 
In [101]:
data_top_ten.groupby('locality_name')['price_per_meter'].agg('mean'
                    ).round(2).sort_values(ascending=False)
Out[101]:
locality_name
Санкт-Петербург     111,204.75
Пушкин              103,047.05
деревня Кудрово      92,473.55
посёлок Парголово    90,332.26
посёлок Мурино       85,673.26
посёлок Шушары       78,551.34
Колпино              75,333.30
Гатчина              68,746.15
Всеволожск           68,719.32
Выборг               58,172.39
Name: price_per_meter, dtype: float64

Населенные пункты с самой высокой стоимостью квадратного метра из ТОП-10 по количеству объявлений

In [102]:
print(data_top_ten[data_top_ten['price_per_meter'] == 
      data_top_ten['price_per_meter'].max()]['locality_name'].unique())
['Санкт-Петербург']
In [103]:
data_top_ten[data_top_ten['price_per_meter'] == data_top_ten['price_per_meter'].max()]
Out[103]:
total_images last_price total_area first_day_exposition rooms ceiling_height floors_total living_area floor is_apartment ... ponds_around3000 ponds_nearest days_exposition price_per_meter first_exp_weekday first_exp_month first_exp_year type_floor cityCenters_nearest_km type_floor_num
4859 16 28.00 33.00 2019-04-29 1 3.50 5 17.60 2 False ... 3 119.00 <NA> 848,484.85 0 4 2019 другой 1.00 3
17172 14 28.00 33.00 2019-04-30 1 3.50 5 17.60 2 False ... 3 27.00 <NA> 848,484.85 1 4 2019 другой 1.00 3

2 rows × 29 columns

Населенные пункты с самой низкой стоимостью квадратного метра из ТОП-10 по количеству объявлений

In [104]:
print(data_top_ten[data_top_ten['price_per_meter'] == 
      data_top_ten['price_per_meter'].min()]['locality_name'].unique())
['Санкт-Петербург']
In [105]:
min_spb = data_top_ten.query('locality_name == "Санкт-Петербург"')['price_per_meter'].min()
min_spb
Out[105]:
111.83

Минимальное значение цены за квадратный метр выглядит в Санкт-Петербурге очень странно, похоже ошибка в данных. Удаляю эту строку как аномальную и повторяю поиск минимального значения

In [106]:
data_top_ten = data_top_ten.loc[data_top_ten['price_per_meter'] != min_spb]
In [107]:
data_top_ten[data_top_ten['price_per_meter'] == data_top_ten['price_per_meter'].min()]
Out[107]:
total_images last_price total_area first_day_exposition rooms ceiling_height floors_total living_area floor is_apartment ... ponds_around3000 ponds_nearest days_exposition price_per_meter first_exp_weekday first_exp_month first_exp_year type_floor cityCenters_nearest_km type_floor_num
23477 3 1.45 138.00 2018-07-06 3 2.65 2 58.00 2 False ... <NA> NaN 52 10,507.25 4 7 2018 последний NaN 2

1 rows × 29 columns

In [108]:
data_top_ten[data_top_ten['price_per_meter'] == data_top_ten['price_per_meter'].min()]['locality_name']
Out[108]:
23477    Гатчина
Name: locality_name, dtype: object

Вывод: Самая высокая цена за квадратный метр среди 10-ти городов с максимальным количеством квартир в продаже 848484.85 рублей за квадратный метр в Санкт-Петербурге, самая низкая в Свири - 7962.96 рубля. Максимальная средняя цена продажи в объявлениях в этих городах составляет 111,204.75 рублей в городе Санкт-Петербург

Часть 5.¶

Выделить квартиры в Санкт-Петербурге с помощью столбца locality_name и вычислить среднюю цену каждого километра. Описать, как стоимость объектов зависит от расстояния до центра города.¶

In [109]:
data_spb = data[data['locality_name'] == 'Санкт-Петербург'].copy()

Ранее я округлил до целых и привел к типу int данные в столбце расстояние до центра в километрах, поэтому группирую данные по расстоянию до центра города в сводную таблицу, выведу ТОП-5 километров от центра по максимальной средней цене за кв.м и ТОП-5 километров по минимальной цене за кв.м, рассчитаю коэффициент корреляции и построю график, который отражает зависимости

Проверяю пропуски, так как для исследования корреляции мне нужны стобцы с полностью заполненными данными c столбце cityCenters_nearest_km

In [110]:
data_spb.cityCenters_nearest_km.isna().sum()
Out[110]:
52

Учитывая незначительное количество пропущенных данных, просто отброшу строки с пропусками

In [111]:
data_spb = data_spb.dropna(subset=['cityCenters_nearest_km'])
In [112]:
pd.pivot_table(data_spb, index='cityCenters_nearest_km', 
               values=('last_price', 'price_per_meter'), aggfunc=np.mean)
Out[112]:
last_price price_per_meter
cityCenters_nearest_km
0.00 16.73 167,305.31
1.00 11.71 146,048.97
2.00 10.69 129,310.18
3.00 9.57 118,102.69
4.00 10.28 126,919.16
5.00 10.58 133,450.95
6.00 9.99 135,883.85
7.00 9.97 136,204.61
8.00 8.71 123,439.92
9.00 6.77 112,691.45
10.00 6.32 112,660.26
11.00 6.09 108,136.48
12.00 5.77 107,558.54
13.00 6.03 108,157.21
14.00 5.57 104,176.53
15.00 5.76 104,201.23
16.00 5.32 100,482.24
17.00 5.19 96,997.26
18.00 4.81 96,389.65
19.00 5.05 98,658.39
20.00 5.99 103,056.54
21.00 5.49 94,469.58
22.00 5.31 91,405.54
23.00 4.69 92,063.68
24.00 3.85 85,736.90
25.00 4.05 91,531.37
26.00 4.01 87,798.87
27.00 8.30 132,115.71
28.00 5.03 81,161.91
29.00 4.24 72,953.37

ТОП-5 километров от центра города с максимальными средними ценами за кв.м

In [113]:
pd.pivot_table(data_spb, index='cityCenters_nearest_km', 
               values=('last_price', 'price_per_meter'), 
               aggfunc=np.mean).sort_values(by='price_per_meter', ascending=False).head(5)
Out[113]:
last_price price_per_meter
cityCenters_nearest_km
0.00 16.73 167,305.31
1.00 11.71 146,048.97
7.00 9.97 136,204.61
6.00 9.99 135,883.85
5.00 10.58 133,450.95

ТОП-5 километров от центра города с минимальными средними ценами за кв.м

In [114]:
pd.pivot_table(data_spb, index='cityCenters_nearest_km', 
               values=('last_price', 'price_per_meter'), 
               aggfunc=np.mean).sort_values(by='price_per_meter').head(5)
Out[114]:
last_price price_per_meter
cityCenters_nearest_km
29.00 4.24 72,953.37
28.00 5.03 81,161.91
24.00 3.85 85,736.90
26.00 4.01 87,798.87
22.00 5.31 91,405.54
In [115]:
data_spb[['cityCenters_nearest_km', 'last_price', 'price_per_meter']].corr()
Out[115]:
cityCenters_nearest_km last_price price_per_meter
cityCenters_nearest_km 1.00 -0.41 -0.34
last_price -0.41 1.00 0.65
price_per_meter -0.34 0.65 1.00
In [116]:
# scatter plot 'last_price' and 'cityCenters_nearest_km' spb
y=data_spb.last_price
x=data_spb.cityCenters_nearest_km
plt.figure(figsize=(9, 5))
plt.scatter(x, y, s=20, alpha=0.3) 
plt.grid()
plt.title("Зависимость между ценой объекта и расстоянием до центра города")
plt.xlabel("Расстояние до центра города в км.")
plt.ylabel("Цена в млн.руб")
slope, intercept = np.polyfit(x, y, 1)
plt.plot(x, slope*x + intercept, color='red', linestyle='dotted', label='Линия тренда')
plt.legend()
plt.show()

На графике присутствуют объекты, расположенные далее 20-м км от центра с ценой выше 10 млн. рублей. Особенно выделяются 22-й и 27-й км. Посмотрю их ближе, для этого сделаю срез

In [117]:
data_spb.query('(cityCenters_nearest_km == 22 | cityCenters_nearest_km == 27) & last_price > 10')
Out[117]:
total_images last_price total_area first_day_exposition rooms ceiling_height floors_total living_area floor is_apartment ... ponds_around3000 ponds_nearest days_exposition price_per_meter first_exp_weekday first_exp_month first_exp_year type_floor cityCenters_nearest_km type_floor_num
748 13 14.35 74.00 2017-11-28 2 3.13 5 30.00 3 False ... 0 NaN 128 193,918.92 1 11 2017 другой 27.00 3
15985 13 15.30 136.00 2018-03-20 4 2.65 16 86.00 7 False ... 1 458.00 110 112,500.00 1 3 2018 другой 22.00 3
17519 6 16.47 133.40 2018-08-23 3 3.00 4 45.00 4 False ... 2 251.00 <NA> 123,476.00 3 8 2018 последний 22.00 2
21042 0 12.00 60.00 2018-07-07 2 2.65 26 30.50 25 False ... 1 400.00 90 200,000.00 5 7 2018 другой 22.00 3
21712 19 10.20 99.30 2019-02-11 2 2.65 17 30.00 16 False ... 1 1,052.00 61 102,719.03 0 2 2019 другой 22.00 3

5 rows × 29 columns

Вывод Очевидно, что корелляция между расстоянием от центра Санкт-Петербурга и ценой отрицательная, то есть чем больше расстояние от центра города, тем ниже цена. Однако на расстоянии от 22 до 27 километра присутствуют необычно дорогие объекты в общем количестве 5 штук. При ближайшем рассмотрении вижу, что это крупногабаритные квартиры с высокими потолками видимо в домах современной постройки. Могу предположить, что это могут быть квартиры повышенного качества, бизнесс класс или элитное жилье в курортных районах Санкт-Петербурга, например в локациях Ольгино, Лисий Нос. Учитывая их незначительное количество, такие квартиры не оказывают серьезного влияния на общую тенденцию

Шаг 5. Общий вывод¶

В процессе исследования были обнаружены и по возможности устранены аномалии в следующих столбцах: этажность зданий, высота потолков, площадь общая, кухни и жилая. Также отсечены слишком дорогие квартиры, стоимость которых могла влиять на результат.

В датасете было обнаружено большое количество картографических данных, при этом пропуски по нескольким столбцам в ощутимом по размерам объеме, особенно по Ленинградской области. Полагаю, это связано с какими-то техническими вопросами, потому что пользователи обычно совершают разные ошибки, а в этом случае прослеживается одна и таже закономерность на большом объеме. Отсутствие данных возможно из-за настроек автоматического заполнения расстояний по геопозиции. Принято решение эти пропуски не заполнять.

Исследованы зависимости различных параметров друг от друга: цена от расстояния до центра, цена от площадей общей, жилой, кухни; цена от количества комнат. Также исследованы особенности рынка жилой недвижимости Санкт-Петербурга.

Выводы:

  • Большую часть предложения на рынке за исследованные годы (2014-2019) составляли одно- и двухкомнатные квартиры общей площадью от 30 до 65 кв.м. с ценами в диапазоне от 3 до 7 млн.рублей.
  • Очевидная зависимость наблюдается между размерами квартиры и ее стоимостью в сравнении с аналогами в той же локации.
  • Существует прямая зависимость удаленности квартиры от центра города. Расчетные величины показывают, что чем больше расстояние от центра города, тем меньше цена. Да, в общем понимании это действительно так. Однако, если мы внимательно посмотрим, то окажется, что на расстоянии 5-7 км есть значения средней цены за квадратный метр не намнного ниже, чем в самом центре города. Этот факт можно объяснить следующим: цены на любом рынке определяются балансом спроса/предложения. В нашем случае основной товар на рынке жилой недвижимости - это квартиры для собственного проживания. И покупатели стремятся поселиться в районах, предназначенных для проживания (без промышленных и туристических объектов), насыщенных гражданской инфраструктурой (школами, детскими садами, учебными заведениями, медицинскими учреждениями). При этом, конечно важна близость к центру, потому что это: меньше пробок, экономия времени по пути работа-дом, и конечно, более высокая ликвидность приобретенной недвижимости. Этот факт подтвержадают данные о том, что основной объем предложения мы видим в "поясе" примерно от 5 до 15 километров от центра города. И в самом начале этого "пояса" (5-7 км от центра) цены не ниже, чем в самом центре.
  • Также на цену влияют год постройки жилого дома и развитость инфраструктуры конкретного района.